﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace DanMuHelper.Editor
{
    internal static partial class CodeGenerator
    {
        const string TagAndLayerNamesVersion = "1.0.0";

        const string MenuName = "📺 Coding/";

        [MenuItem(MenuName + "Tag + Layer")]
        private static void RegenerateTagAndLayerNames()
        {
            GenerateTagAndLayerNames();
        }

        private static void GenerateTagAndLayerNames()
        {
            var layerNames = GetLayerNames();
            //Debug.LogFormat("LayerNames: {0}", string.Join(", ", layerNames));
            var tagNames = GetTagNames();
            //Debug.LogFormat("TagNames: {0}", string.Join(", ", tagNames));
            var allTypes = AppDomain
                .CurrentDomain.GetAssemblies()
                .Where(
                    a =>
                        !a.IsDynamic
                        && !a.FullName.StartsWith("UnityEngine")
                        && !a.FullName.StartsWith("UnityEditor")
                        && !a.FullName.StartsWith("mscorlib")
                        && !a.FullName.StartsWith("System")
                        && !a.FullName.StartsWith("Mono")
                        && !a.FullName.StartsWith("netstandard")
                )
                .SelectMany(a => a.GetTypes()); 

            var typeTagNames = allTypes.Where(t => t.FullName == "DanMuHelper.Generated.TagNames").ToArray();
            var typeLayerNames = allTypes.Where(t => t.FullName == "DanMuHelper.Generated.LayerNames").ToArray();
            if (typeTagNames.Length > 1)
            {
                Debug.LogErrorFormat(
                    "More than one type named TagNames: {0}",
                    string.Join(", ", typeTagNames.Select(t => t.AssemblyQualifiedName))
                );
                return;
            }
            if (typeLayerNames.Length > 1)
            {
                Debug.LogErrorFormat(
                    "More than one type named LayerNames: {0}",
                    string.Join(", ", typeLayerNames.Select(t => t.AssemblyQualifiedName))
                );
                return;
            }
            bool dirty = false;
            var codeLayers = CreateNames(layerNames, typeLayerNames.FirstOrDefault(), "LayerNames");
            if (codeLayers != null)
            {
                if (!Directory.Exists("Assets/DanMuHelper.Generated/Runtime"))
                {
                    Directory.CreateDirectory("Assets/DanMuHelper.Generated/Runtime");
                }
                var codePath = "Assets/DanMuHelper.Generated/Runtime/LayerNames.cs";
                File.WriteAllText(codePath, codeLayers);
                dirty = true;
            }
            var codeTags = CreateNames(tagNames, typeTagNames.FirstOrDefault(), "TagNames");
            if (codeTags != null)
            {
                if (!Directory.Exists("Assets/DanMuHelper.Generated/Runtime"))
                {
                    Directory.CreateDirectory("Assets/DanMuHelper.Generated/Runtime");
                }
                var codePath = "Assets/DanMuHelper.Generated/Runtime/TagNames.cs";
                File.WriteAllText(codePath, codeTags);
                dirty = true;
            }
            if (dirty)
            {
                AssetDatabase.Refresh();
            }
        }

        private static string CreateNames(string[] names, Type targetType, string targetTypeName)
        {
            // 反射先查询类型中已经定义的字段, 如果已经存在, 则不再创建, 如果存在, 但是新列表中没有, 则标记为Obsolete
            Dictionary<string, string> scriptedfiledNames = new Dictionary<string, string>();
            // 代码中标记为Obsolete的字段, 保留, 避免编译错误
            Dictionary<string, string> scriptedObsoleteNames = new Dictionary<string, string>();
            if (targetType != null)
            {
                var fields = targetType.GetFields(
                    BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly
                );
                foreach (var field in fields)
                {
                    if (field.IsLiteral && field.FieldType == typeof(string))
                    {
                        // literal field
                        var value = (string)field.GetValue(null);
                        scriptedfiledNames.Add(value, field.Name);
                        if (field.IsDefined(typeof(ObsoleteAttribute), false))
                        {
                            scriptedObsoleteNames.Add(value, field.Name);
                        }
                    }
                }
            }

            var validNames = names.Where(n => !string.IsNullOrWhiteSpace(n)).ToHashSet();
            //Debug.Log("valid names: " + string.Join("|", validNames));
            //Debug.Log("scripted names: " + string.Join("|", scriptedfiledNames.Keys));
            //Debug.Log("scripted obsolete names: " + string.Join("|", scriptedObsoleteNames.Keys));
            //Debug.Log("scripted valid names: " + string.Join("|", scriptedfiledNames.Keys.Except(scriptedObsoleteNames.Keys)));
            // 停止生成的条件如下:
            // 1. 现有代码中非Obsolete的字段集合=当前名字集合
            if (scriptedfiledNames.Keys.Except(scriptedObsoleteNames.Keys).ToHashSet().SetEquals(validNames))
            {
                Debug.Log($"[{targetTypeName}] No change, do nothing.");
                return null;
            }
            //var newObsoleteNames = scriptedfiledNames.Keys.Except(scriptedObsoleteNames.Keys).Except(validNames);
            //// 现有代码如果已经包含所有当前名字, 则不需要重新生成
            //if (
            //    validNames
            //        .Except(scriptedfiledNames.Keys.Except(scriptedObsoleteNames.Keys))
            //        .Count() == 0
            //    && scriptedObsoleteNames.Keys.Except(newObsoleteNames).Count() == 0
            ////scriptedfiledNames.Keys.Except(scriptedObsoleteNames.Keys).ToHashSet().Equals(validNames)
            //)
            //{
            //    Debug.Log("No change, do nothing.");
            //    return null;
            //}

            var codeDeclare = new List<string>();
            var codeObsolete = new List<string>();
            foreach (var name in validNames.OrderBy(n => n))
            {
                var csharpName = EnsureCSharpIdentifier(name);
                if (csharpName == null)
                {
                    Debug.LogError("无法将名称转换为C#字段: " + name);
                    continue;
                }
                codeDeclare.Add(string.Format("public const string {0} = \"{1}\";", csharpName, name));
            }
            // 保留已经编码的obsolete字段, 避免编译错误
            foreach (var name in scriptedfiledNames.Keys.OrderBy(n => n))
            {
                if (!validNames.Contains(name))
                {
                    codeObsolete.Add(string.Format("[System.Obsolete(\"{0} 已移除, 代码无引用可移除此字段\")]", name));
                    codeObsolete.Add(string.Format("public const string {0} = \"{1}\";", name, name));
                }
            }

            var codelines = new List<string>();
            var tmpl =
                @"// This file is auto generated by danmuhelper.CodingUnityNames
// Generated at #GENERATEDATETIME#

namespace DanMuHelper.Generated
{
    /* 代码生成脚本, 如有需要, 仅仅移除Obsolete字段, 不要修改其他内容! */
    // [danmuhelper.VersionedScript(#SCRIPT_VERSION#)]
    public static class #CLASSNAME#
    {
#OBSOLETE#
#DECLARE#

        /// <summary>
        /// All names.
        /// </summary>
#ALLNAMES#
    }
}
";
            tmpl = tmpl.Replace("\r\n", "\n");
            tmpl = tmpl.Replace("#CLASSNAME#", targetTypeName);
            tmpl = tmpl.Replace("#SCRIPT_VERSION#", $"\"{TagAndLayerNamesVersion}\"");
            tmpl = tmpl.Replace("#OBSOLETE#", string.Join("\n", codeObsolete.Select(s => new string(' ', 8) + s)));
            tmpl = tmpl.Replace("#DECLARE#", string.Join("\n", codeDeclare.Select(s => new string(' ', 8) + s)));
            tmpl = tmpl.Replace("#GENERATEDATETIME#", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            var allNames = validNames.Concat(scriptedfiledNames.Keys).ToArray();
            Array.Sort(allNames);
            var codelineAllNames = new List<string>() { "public static readonly string[] @AllNames = new string[] {", };
            codelineAllNames.AddRange(allNames.Select(n => new string(' ', 4) + $"\"{n}\","));
            codelineAllNames.Add("};");
            tmpl = tmpl.Replace("#ALLNAMES#", string.Join("\n", codelineAllNames.Select(s => new string(' ', 8) + s)));
            //var codeAllNames = string.Join(",\n", allNames.Select(n => $"\"{n}\""));
            //var codeLine = string.Format(
            //    "public static readonly string[] @AllNames = new string[] {{ {0} }};",
            //    codeAllNames
            //);
            //tmpl = tmpl.Replace("#ALLNAMES#", new string(' ', 8) + codeLine);
            return tmpl;
        }

        private static string[] GetLayerNames()
        {
            var layers = new string[32];
            for (var i = 0; i < 32; i++)
            {
                layers[i] = LayerMask.LayerToName(i);
            }
            return layers;
        }

        private static string[] GetTagNames()
        {
            return UnityEditorInternal.InternalEditorUtility.tags;
        }
    }
}
